Apache Iceberg resolvió hace tiempo el problema de tener tablas gigantes en un almacén de objetos sin perder consistencia. Lo que todavía generaba dudas era otra cosa: si construyes un servidor MCP para que un agente lea esas tablas, ¿hace falta escribir un servidor distinto por cada catálogo, o basta con uno solo? Un desarrollador que firma como xbill9 se puso a comprobarlo con siete catálogos reales a la vez, y publicó el código y los resultados en abierto.
El repositorio se llama lakehouse-iceberg-2026, y la pieza que nos interesa vive en iceberg-mcp-hosts/servers/iceberg_mcp.py: un servidor MCP de menos de lo que esperarías, sin el SDK oficial de MCP de por medio, que habla JSON-RPC línea a línea por stdin/stdout. Nada de dependencias que puedan romperse con el tiempo.
Cuatro herramientas, todas de solo lectura
El servidor expone exactamente cuatro tools, y las cuatro son de solo lectura, nada de escrituras:
iceberg_list_tables todas las tablas del catálogo, comonamespace.tablaiceberg_describe_table columnas, particionado, snapshots y la ubicación del metadatoiceberg_count_rows el número exacto de filas, sacado del resumen del snapshoticeberg_scan_table unas cuantas filas de muestra, más un conteo exacto y el mínimo y máximo de una columna
La gracia está en que el protocolo REST de Iceberg es el mismo lo mires donde lo mires, así que en teoría el mismo servidor debería funcionar contra cualquier catálogo que lo hable. La prueba consistió en comprobar justo eso, sin ningún modelo de por medio: las cuatro tools se llaman directamente por MCP, así que cada resultado depende solo del catálogo y de cómo esté configurado el servidor, no de que una IA interprete nada.
Siete catálogos, y solo uno se quedó fuera
La lista incluye Apache Polaris 1.7.0 en Docker local, y seis catálogos gestionados por internet: Google BigLake, Microsoft OneLake, AWS Glue, AWS S3 Tables, Snowflake Horizon y Databricks Unity. Este último se quedó fuera de las pruebas porque la cuenta de prueba había caducado, así que el marcador final es de seis sobre siete.
Catálogo | Tabla | Filas | Particionado | list / describe / count / scan (s) |
Apache Polaris | probe_ns.probe_table | 11 | ts_day | 1.0 / 0.5 / 0.0 / 0.1 |
AWS Glue | probe_ns.probe_table | 11 | ts_day | 2.1 / 0.7 / 0.2 / 1.5 |
AWS S3 Tables | probe_ns.probe_table | 11 | ts_day | 2.2 / 0.3 / 0.2 / 2.0 |
Google BigLake | probe_ns.probe_table | 11 | ts_day | 10.1 / 1.6 / 0.5 / 3.6 |
Microsoft OneLake | dbo.probe_table | 6 | sin particionar | 2.5 / 0.2 / 0.2 / 3.2 |
Snowflake Horizon | PROBE_NS.PROBE_TABLE | 12 | ts_day | 7.3 / 2.2 / 1.4 / 1.9 |
Las cuatro tools funcionaron en los seis catálogos, sin un solo error. Lo curioso son las diferencias de estilo entre catálogos, que el servidor no tiene que normalizar: OneLake llama a su namespace dbo, Horizon devuelve los nombres en mayúsculas y con menos o más filas de las que tenían las otras tablas, porque cada una se creó por separado en pruebas anteriores del mismo proyecto. El servidor simplemente devuelve lo que el catálogo dice tener, sin tocarlo, así que un cliente MCP no necesita ningún caso especial para leer dbo.probe_table o PROBE_NS.PROBE_TABLE.
Lo que de verdad se rompe: dependencias que pyiceberg no trae de serie
Listar, describir y contar filas solo necesita leer metadatos, así que esas tres tools funcionan con pyiceberg a pelo. Pero iceberg_scan_table lee los ficheros de datos de verdad, y ahí es donde aparecen los fallos si te falta algo:
CATALOG ERROR while scanning dbo.probe_table: ModuleNotFoundError: No module named 'adlfs'. CATALOG ERROR while scanning probe_ns.probe_table: ModuleNotFoundError: No module named 's3fs'. CATALOG ERROR while listing tables: MissingDependencyException: Missing Dependency: Using the login credential provider requires an additional dependency. You will need to pip install "botocore[crt]" before proceeding.
La solución es instalar tres paquetes que pyiceberg no trae por defecto:
pip install adlfs s3fs "botocore[crt]"
adlfs hace falta para leer datos de OneLake, s3fs para S3 Tables, y botocore[crt] en cuanto el login de AWS se haga con el comando moderno aws login en vez de las claves clásicas.
553 segundos contra 0,6: el coste de dejar que Azure adivine cómo autenticarte
El hallazgo más llamativo de toda la prueba tiene que ver con OneLake. Para leer los ficheros de datos hace falta, además del token del catálogo, una credencial de almacenamiento de Azure. La opción por defecto de las librerías de Azure, DefaultAzureCredential, prueba primero el servicio de metadatos de una máquina virtual de Azure antes de caer en tu sesión de az login. Si no estás corriendo dentro de Azure, esa comprobación agota sus reintentos antes de rendirse:
917ms No environment configuration found. 921ms ManagedIdentityCredential will use IMDS 553989ms DefaultAzureCredential acquired a token from AzureCliCredential AzureCliCredential 0.6s DefaultAzureCredential 553.1s
A través del servidor MCP, ese mismo escaneo de tres filas de OneLake tardó 858,5 segundos con DefaultAzureCredential y 3,4 segundos usando AzureCliCredential directamente. El resto de tools no se veía afectado, porque solo el escaneo lee ficheros y necesita esa credencial extra. Si trabajas fuera de una VM de Azure, saltarte DefaultAzureCredential y apuntar directamente a AzureCliCredential ahorra casi diez minutos por llamada.
Snowflake Horizon regala credenciales de S3 sin que nadie las pida
El otro detalle que merece una nota aparte: cuando el servidor carga una tabla de Horizon, el lector de ficheros se queda con unas credenciales de S3 completas (clave de acceso, clave secreta, token de sesión y su caducidad), aunque la entrada de ese catálogo en catalogs.yaml no pide ningún dato de almacenamiento. Horizon las entrega por su cuenta con cada carga de tabla. Funciona, pero significa que cualquier log de esa respuesta lleva una credencial viva dentro y hay que tratarlo en consecuencia.
Algo parecido, aunque de forma explícita, pasa con S3 Tables: ese catálogo entrega una credencial de almacenamiento cuando el cliente la pide con la cabecera X-Iceberg-Access-Delegation: vended-credentials. La diferencia es que ahí el cliente tiene que pedirlo; en Horizon llega sin preguntar.
Cómo probarlo tú mismo
Para el catálogo local con Apache Polaris solo hace falta Docker y Python 3.10 o superior (la prueba original se hizo con Python 3.14.7 y pyiceberg 0.12.0):
git clone https://github.com/xbill9/lakehouse-iceberg-2026 cd lakehouse-iceberg-2026/iceberg-conformance ./polaris-up.sh export POLARIS_CLIENT_ID=root POLARIS_CLIENT_SECRET=s3cr3t python3 seed_table.py --catalog apache-polaris
Eso deja lista una tabla de prueba, probe_ns.probe_table, con 11 filas particionadas por día. A partir de ahí, el servidor se lanza indicándole qué catálogo usar con dos variables de entorno:
ICEBERG_CATALOG=apache-polaris ICEBERG_CATALOGS_FILE=catalogs.yaml python3 servers/iceberg_mcp.py
Y para llamarlo sin necesidad de un cliente MCP completo, el propio repositorio trae un script que hace de barrido automático:
cd ../iceberg-mcp-hosts python3 sweep_catalogs.py --only apache-polaris
Si quieres engancharlo a un agente de verdad, en Claude Code basta con un .mcp.json de proyecto apuntando al script y fijando el catálogo por variable de entorno:
{
"mcpServers": {
"iceberg": {
"command": "python3",
"args": ["iceberg-mcp-hosts/servers/iceberg_mcp.py"],
"env": {
"ICEBERG_CATALOG": "aws-glue",
"ICEBERG_CATALOGS_FILE": "iceberg-conformance/catalogs.yaml"
}
}
}
}
Para saltar a cualquier otro de los seis catálogos que sí respondieron solo hay que cambiar el valor de ICEBERG_CATALOG y añadir su entrada correspondiente en catalogs.yaml con el login que le toque: OAuth2 para Polaris, gcloud para BigLake, az para OneLake, y las credenciales SigV4 de AWS para Glue y S3 Tables.
Lo que se lleva de esta prueba
El código del servidor no cambió ni una línea entre catálogo y catálogo: la única diferencia real fue una variable de entorno y una entrada en un YAML. Eso es justo lo que promete el estándar REST de Iceberg, y aquí queda comprobado con medidas de verdad, no solo sobre el papel. Lo que sí hace falta vigilar son las tres dependencias fuera de lo que trae pyiceberg por defecto, elegir bien qué credencial de Azure usar si no vives dentro de Azure, y tener presente que algunos catálogos, como Horizon, entregan credenciales de almacenamiento sin que se las pidas.
Todo lo anterior está basado en el trabajo original de xbill9, publicado en dev.to y con el código completo disponible en GitHub. Si quieres profundizar en las piezas que hay detrás: la especificación de Model Context Protocol, el proyecto PyIceberg y Apache Polaris, el catálogo REST que se puede levantar en local con un solo script.
Imagen: Pexels / Jonathan Cooper
